package com.young.bs.vote.service.impl;

import com.young.bs.vote.model.VoteInfo;
import com.young.bs.vote.model.VotePerson;
import com.young.bs.vote.model.VoteResult;
import com.young.bs.vote.service.IVoteInfoService;
import com.young.bs.vote.service.IVotePersonService;
import com.young.bs.vote.service.IVoteResultService;
import com.young.bs.vote.service.IVoterService;
import com.young.bs.vote.util.VoteConstants;
import com.young.common.exception.BaseException;
import com.young.common.util.AESUtil;
import com.young.common.util.CommonUtil;
import com.young.common.util.RSAUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Date;
import java.util.List;

/**
 * xxx
 *
 * @author imrookie
 * @date 2019/3/27
 */
@Service
public class VoterServiceImpl implements IVoterService {

    private static final Logger logger = LoggerFactory.getLogger(VoterServiceImpl.class);

    @Resource(name="voteInfoService")
    IVoteInfoService voteInfoService;

    @Resource(name="voteResultService")
    IVoteResultService voteResultService;

    @Resource(name="votePersonService")
    IVotePersonService votePersonService;

    /**
     * 发起投票
     *
     * @param voteId   投票id
     * @param optionId 选项id
     * @param userId   用户id
     * @return
     */
    @Override
    public void initiate(String voteId, String optionId, String userId) {
        //根据投票id查询投票信息以及该用户的投票记录
        VoteInfo voteInfo = voteInfoService.getDetail(voteId, userId);
        if (voteInfo == null || !voteInfo.refreshCanVoteFlag()) {
            throw new BaseException("该投票信息不可用");
        }
        if (voteInfo.getVoteResult() != null) {
            throw new BaseException("不允许重复发起投票");
        }

        //判断用户是否在投票人列表中
        VotePerson votePerson = new VotePerson();
        votePerson.setVoteId(voteId);
        votePerson.setUserId(userId);
        List<VotePerson> list =  votePersonService.getList(votePerson);
        if (list == null || list.isEmpty()) {
            throw new BaseException("您无此投票权");
        }

        VoteResult voteResult = new VoteResult();
        voteResult.setId(CommonUtil.getUUID());
        voteResult.setVoteId(voteId);
        voteResult.setVoteOptionId(optionId);
        voteResult.setVoterId(userId);

        voteResult.setVoteTime(new Date());//投票时间
        try {
            voteResult.setBitPromise(AESUtil.generateKey());//比特承诺数, 使用AES算法生成的密钥作为比特承诺数
            //使用AES算法进行比特承诺
            voteResult.setBitValue(AESUtil.encript(optionId, voteResult.getBitPromise()));
        } catch (Exception e) {
            throw new BaseException("比特承诺失败");
        }

        try {
            String[] keys = RSAUtil.generateKeyPair();//生成密钥对
            voteResult.setVoterPublicKey(keys[0]);//投票者公钥
            voteResult.setVoterPrivateKey(keys[1]);//投票者私钥
        } catch (Exception e) {
            throw new BaseException("生成密钥对失败");
        }
        /*voteResult.setVoterPublicKey(VoteConstants.VOTER_RSA_KEY_PUBLIC);//投票者公钥
        voteResult.setVoterPrivateKey(VoteConstants.VOTER_RSA_KEY_PRIVATE);//投票者私钥*/

        //投票者使用私钥对比特承诺结果进行盲化
        try {
            voteResult.setBlindValue(Base64.getEncoder().encodeToString(RSAUtil.encryptByPrivate(voteResult.getBitValue().getBytes(), voteResult.getVoterPrivateKey())));
        } catch (Exception e) {
            throw new BaseException("盲化失败");
        }

        //入库
        voteResultService.insert(voteResult);
    }

    /**
     * 管理员认证
     *
     * @param resultId    投票结果id
     * @param adminUserId 管理员id
     */
    @Override
    public void approve(String resultId, String adminUserId) {
        //查询结果记录
        VoteResult voteResult = voteResultService.get(resultId);

        logger.info("[管理员认证] 根据结果id={}查询投票结果信息={}", resultId, voteResult);

        //数据校验
        Assert.notNull(voteResult, "投票结果无效");
        Assert.hasLength(voteResult.getBlindValue(), "盲化值无效");
        Assert.hasLength(voteResult.getVoterPublicKey(), "投票者公钥无效");
        Assert.hasLength(voteResult.getBitValue(), "比特承诺结果无效");
        Assert.notNull(voteResult.getState(), "投票结果状态无效");
        Assert.isTrue(voteResult.getState() == 1, "[发起投票]状态的投票结果才允许管理员认证");

        //根据投票者公钥对盲化值进行解密
        String bitValue = null;//比特承诺结果
        try {
//            bitValue = Base64.getEncoder().encodeToString(RSAUtil.decryptByPublic(Base64.getDecoder().decode(voteResult.getBlindValue()), voteResult.getVoterPublicKey()));
            bitValue = new String(RSAUtil.decryptByPublic(Base64.getDecoder().decode(voteResult.getBlindValue()), voteResult.getVoterPublicKey()));
            logger.info("[管理员认证] 根据投票者公钥对盲化值进行解密,计算出的比特承诺结果={}", bitValue);
        } catch (Exception e) {
            throw new BaseException("盲化结果解密失败");
        }

        Assert.isTrue(voteResult.getBitValue().equals(bitValue), "比特承诺结果校验失败(公钥解密后匹配失败)");

        //比特承诺结果校验成功后,管理员对投票结果进行签名认证
        VoteResult updateResult = new VoteResult();
        updateResult.setId(voteResult.getId());
        updateResult.setAdminPublicKey(VoteConstants.ADMIN_RSA_KEY_PUBLIC);
        updateResult.setAdminPrivateKey(VoteConstants.ADMIN_RSA_KEY_PRIVATE);
        updateResult.setAdminSignTime(new Date());//签名时间
        try {
            //使用管理员私钥对比特承诺结果签名
            updateResult.setAdminSignValue(Base64.getEncoder().encodeToString(RSAUtil.encryptByPrivate(bitValue.getBytes(), updateResult.getAdminPrivateKey())));
        } catch (Exception e) {
            throw new BaseException("管理员签名失败");
        }
        updateResult.setState(2);//状态更新为 管理员签名

        //更新库
        voteResultService.update(updateResult);

    }

    /**
     * 计票
     *
     * @param resultId    投票结果id
     * @param countUserId 计票员id
     */
    @Override
    public void counting(String resultId, String countUserId) {
        //查询结果记录
        VoteResult voteResult = voteResultService.get(resultId);

        logger.info("[计票] 根据结果id={}查询投票结果信息={}", resultId, voteResult);

        //数据校验
        Assert.notNull(voteResult, "投票结果无效");
        Assert.isTrue(voteResult.getState() == 2, "[管理员签名]状态的投票结果才允许被计票");

        //校验管理员签名是否合法
        String deSign = null;//应该等于比特承诺结果
        try {
            deSign = new String(RSAUtil.decryptByPublic(Base64.getDecoder().decode(voteResult.getAdminSignValue()), voteResult.getAdminPublicKey()));//管理员公钥解密签名
        } catch (Exception e) {
            throw new BaseException("管理员签名解密失败");
        }

        //判断解密内容是否与比特承诺结果一致
        Assert.isTrue(voteResult.getBitValue().equals(deSign), "管理员签名有问题");

        //根据比特承诺数反算投票结果
        String res = null;
        try {
            res = AESUtil.decript(deSign, voteResult.getBitPromise());
        }  catch (Exception e) {
            throw new BaseException("解密投票结果失败");
        }

        Assert.isTrue(voteResult.getVoteOptionId().equals(res), "计票员反算投票结果与库值不一致,投票记录存在问题");

        VoteResult updateResult = new VoteResult();
        updateResult.setId(voteResult.getId());
        updateResult.setCountCenterValue(deSign);
        updateResult.setCountCenterTime(new Date());
        updateResult.setState(3);//状态更新为 计票中心计票
        //更新库
        voteResultService.update(updateResult);
    }
}
